﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
using System.Windows;
using GalaSoft.MvvmLight.Messaging;
using HtmlAgilityPack;
using mevoronin.RuCaptchaNETClient;
using NLog;
using SellAvi.Model;
using SellAvi.Properties;
using SellAvi.Views;

namespace SellAvi.Classes
{
    public class TaskProductUploader : AvitoTask
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        private readonly NameValueCollection _defaultPostFields = new NameValueCollection
        {
            {"private","0"},
            //{"seller_name", @"Компания ""Иколайн"""},
            //{"manager", "Владимир"},
            //{"email", "iko-line@yandex.ru"},
            {"allow_mails","1"},
            {"authState","phone-edit"},
            //{"phone", "8 911 724-68-12"},
            //{"location_id", "653240"}, //region
            //{"root_category_id", "2"},
            {"source","add"},
            {"metro_id", ""},
            {"district_id", ""},
            {"road_id", ""},
            {"streetGuid",""},
            //{"category_id", "19"},
            //{"params[44]", "4814"},
            //{"title", "Двери межкомнатные трио капелла установка"},
            //{"description", @"Целиком и полностью несем ответственность за проделанную работу. Выезд мастера бесплатно! Разумная цена."},
            //{"price", "1890"},
            {"image",""},
            {"videoUrl",""},
            {"service_code", "free"},
            {"main_form_submit", "Продолжить с пакетом «Обычная продажа»"}
            //{"fees[parentLocationId]",""}
            
            //{"token[8504943741055]", "d88381a05549080"},
            //images[]=1869677597
            //rotate[1869677597]=0
            //image=
        };

        private static int _captchaCounter;
        private readonly AvitoProduct _avitoProduct;
        private readonly AvitoTaskStatus _avitoTaskStatus;
        public string ResolvedCaptchaText { get; set; }





        public TaskProductUploader(AbstractModel am)
            : base(am)
        {
            _postFieldsCollection = new NameValueCollection { _defaultPostFields };
            _avitoProduct = am as AvitoProduct;
            _avitoTaskStatus = new AvitoTaskStatus();
            _captchaCounter = Settings.Default.CaptchaTrials;

        }



        private string _captchaImagePath;
        public string CaptchaImagePath
        {
            get
            {
                return _captchaImagePath != null ?
                    Path.GetFullPath(_captchaImagePath) : null;
            }
            set
            {
                _captchaImagePath = value;
            }
        }

        private readonly NameValueCollection _postFieldsCollection;


        /// <summary>
        /// Копирование характеристик товара в post-коллекцию перед отправкой
        /// </summary>
        /// <param name="pf"></param>
        /// <param name="ap"></param>
        private void SetProductPostFields(AvitoProduct ap)
        {
            Logger.Trace("Заполнение PostFields для отправки");
            var regionId = Settings.Default.PublicationCommonUserRegion ? ap.AvitoUser.TRegion_Id : ap.TRegion_Id;
            var productPostValueCollection = new NameValueCollection()
            {
                {"seller_name", ap.AvitoUser.CompanyName},
                {"manager", ap.AvitoUser.CompanyManager},
                {"email", ap.AvitoUser.CompanyEmail},
                {"phone", ap.AvitoUser.SplitedCompanyPhones.FirstOrDefault()},
                //ранее параметр назывался location_id
                {"locationId", regionId.ToString()},
                {"root_category_id",ap.Category.RootCategoryId.ToString()},
                {"category_id", ap.Category.Id.ToString()},
                //{"params[" + ap.Param.ParamValue + "]", ap.Param.Id.ToString()},
                {"title", ap.Title},
                {"description", Regex.Replace(ap.Description.Replace("<br>", "\r\n"), @"<[^>]*>", String.Empty)},
                {"description_html", Regex.Replace(ap.Description.Replace("<br>", "\r\n"), @"<[^>]*>", String.Empty)},
                {"price", ap.Price},
                //пакеты размещений /*single/package*/
                {"fees[purchase]","single"},
                {"fees[locationId]", regionId.ToString()},
                {"fees[parentLocationId]",regionId.ToString()},
                {"fees[microcategories][]","17"},
                {"fees[quantity]","10"},
                {"fees[eula]","1"},
            };
            //!предварительная очистка
            foreach (var existingKey in productPostValueCollection.AllKeys)
            {
                _postFieldsCollection.Remove(existingKey);
            }
            _postFieldsCollection.Add(productPostValueCollection);
            if (ap.Param != null)
            {
                //очистка параметров
                foreach (var existingParam in _postFieldsCollection.AllKeys.Where(x => x.Contains("params")))
                {
                    _postFieldsCollection.Remove(existingParam);
                }
                //добавление обычного параметра (если существует)
                _postFieldsCollection.Add("params[" + ap.Param.ParamValue + "]", ap.Param.Id.ToString());
                //добавление пользовательских параметров (параметров обучения)
                var customProductParams = ap.Param.TCustomParams;
                var uniqueParams = customProductParams.GroupBy(x => x.CustomParamValue);
                foreach (var groupedParams in uniqueParams)
                {
                    var customParam = customProductParams.LastOrDefault(x => x.CustomParamValue == groupedParams.Key) as TCustomParam;
                    if (customParam != null)
                    {
                        _postFieldsCollection.Add("params[" + customParam.CustomParamValue + "]", customParam.CustomParamId.ToString());

                    }
                }
                Logger.Debug("Добавлено пользовательских параметров: {0}", uniqueParams.Count());
            }
        }

        /// <summary>
        /// Предварительная задержка перед публикацией 
        /// </summary>
        private AvitoTaskStatus PublicationTimeout()
        {
            var commandName = String.Format("Публикация начнется через {0} секунд ...", TaskDelay);
            Logger.Trace(commandName);
            _avitoTaskStatus.OperationStartTime = DateTime.Now;
            _avitoTaskStatus.OperationMessage = commandName;
            Thread.Sleep(1000);
            TaskDelay--;
            _avitoTaskStatus.OperationSucceeded = true;
            _avitoTaskStatus.OperationRepeatNeeded = (TaskDelay > 0) ? true : false;
            return _avitoTaskStatus;
        }

        /// <summary>
        /// Получение ссылки на товар и сохранение AvitoProduct
        /// </summary>
        /// <returns></returns>
        private AvitoTaskStatus FinishPublication()
        {
            var commandName = "Получение обратной ссылки на товар";
            Logger.Trace(commandName);
            _avitoTaskStatus.OperationStartTime = DateTime.Now;
            _avitoTaskStatus.OperationMessage = commandName;

            var htmlPage = new HtmlDocument();
            htmlPage.LoadHtml(WebClient.ResponseHtml);
            var publicationNode =
                htmlPage.DocumentNode.SelectSingleNode("//div[contains(@class, 'finish')]/div/div/p/a[@href]");
            if (publicationNode == null)
            {
                _avitoTaskStatus.OperationSucceeded = false;
                _avitoTaskStatus.ErrorCode = 71;
            }
            else
            {
                _avitoTaskStatus.OperationSucceeded = true;
                _avitoProduct.DatePosted = DateTime.Now;
                _avitoProduct.PostedOnUrl = "https://www.avito.ru" + publicationNode.Attributes["href"].Value;
                Logger.Debug("Ссылка размещения товара: {0}", _avitoProduct.PostedOnUrl);

            }
            return _avitoTaskStatus;
        }

        /// <summary>
        /// Отправка защитного кода на сервер (переход на finish страницу)
        /// </summary>
        private AvitoTaskStatus SendConfirmationCode()
        {
            var commandName = "Отправка защитного кода";
            Logger.Trace(commandName);
            _avitoTaskStatus.OperationStartTime = DateTime.Now;
            _avitoTaskStatus.OperationMessage = commandName;


            WebClient.DefaultHeaders[HttpRequestHeader.Accept] = "application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, */*";
            WebClient.DefaultHeaders[HttpRequestHeader.Referer] = "https://www.avito.ru/additem/confirm";
            WebClient.DefaultHeaders[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";

            var resp = WebClient.UploadString(new Uri("https://www.avito.ru/additem/confirm"),
                "captcha=" + HttpUtility.UrlEncode(ResolvedCaptchaText) + "&done=&subscribe-position=0");

            if ((WebClient.ResponseUri.AbsoluteUri == "https://www.avito.ru/additem"))
            {
                //? товар опубликован, но не ясно по каким причинам произошел редирект на предыдущую страницу при подтверждении товара 
                _avitoTaskStatus.OperationSucceeded = false;
                _avitoTaskStatus.ErrorCode = 61;
            }
            else if (WebClient.ResponseUri.GetLeftPart(UriPartial.Path) == "https://www.avito.ru/additem/pay_service")
            {
                _avitoTaskStatus.OperationSucceeded = false;
                _avitoTaskStatus.ErrorCode = 62;
                //status.TryToResolveMethod = () => _webClient.OnHtmlChanged();
            }
            else if ((WebClient.ResponseUri.AbsoluteUri == "https://www.avito.ru/additem/confirm"))
            {
                _avitoTaskStatus.OperationSucceeded = false;
                _avitoTaskStatus.OperationStepNumber--;
                _captchaCounter--;
                _avitoTaskStatus.OperationRepeatNeeded = (_captchaCounter > 0) ? true : false;
                //_avitoTaskStatus.TryToResolveMethod = ReEnterCaptchaCode;
                _avitoTaskStatus.ErrorCode = 63;
            }
            else
            {
                _avitoTaskStatus.OperationSucceeded = true;
            }
            return _avitoTaskStatus;

        }

        /// <summary>
        /// Скачивание защитного кода и отправка в сервисы (rucaptcha.com / antigate.com)
        /// </summary>
        /// <returns></returns>
        private AvitoTaskStatus DownloadCaptchaImage()
        {
            //Скачивание изображения защитного кода
            var commandName = "Распознавание защитного кода";
            Logger.Trace(commandName);
            _avitoTaskStatus.OperationStartTime = DateTime.Now;
            _avitoTaskStatus.OperationMessage = commandName;
            CaptchaImagePath = GetAndSaveCaptchaImage(WebClient.ResponseHtml);

            //Отправка изображения на сервис RuCaptcha
            if (Settings.Default.CaptchaIsAutomatic && Settings.Default.CaptchaToken.Length == 32)
            {
                RuCaptchaClient client = new RuCaptchaClient(Settings.Default.CaptchaToken);
                var captchaConfig = new CaptchaConfig();
                captchaConfig.SetSoftId("1444");
                string captcha_id = client.UploadCaptchaFile(CaptchaImagePath, captchaConfig);

                Thread.Sleep(Settings.Default.CaptchaTimeOut * 1000);
                ResolvedCaptchaText = client.GetCaptcha(captcha_id);
                Logger.Debug("От сервиса анти-капча получен ответ: {0}", ResolvedCaptchaText);
            }

            //Если требуется ручной ввод или получена ошибка от сервиса распознавания
            if (ResolvedCaptchaText == null || RuCaptchaClient.Errors.ContainsKey(ResolvedCaptchaText))
            {
                _avitoTaskStatus.OperationSucceeded = false;
                if (Settings.Default.CaptchaIsAutomatic)
                {
                    Logger.Info("Распознавание в автоматическом режиме");
                    _avitoTaskStatus.ErrorCode = 52;
                    _avitoTaskStatus.OperationRepeatNeeded = true;
                }
                else
                {
                    Logger.Info("Распознавание в ручном режиме");
                    _avitoTaskStatus.ErrorCode = 51;
                    //_avitoTaskStatus.TryToResolveMethod = CreateCaptchaConfirmationWindow;
                    _avitoTaskStatus.TryToResolveMethod = ReEnterCaptchaCode;
                }
            }
            else
            {
                _avitoTaskStatus.ErrorCode = null;
                _avitoTaskStatus.OperationRepeatNeeded = false;
                _avitoTaskStatus.OperationSucceeded = true;
            }
            return _avitoTaskStatus;
        }

        /// <summary>
        ///  Загрузка параметров товара на сервер (POST запрос)
        /// </summary>
        private AvitoTaskStatus UploadProduct()
        {
            var commandName = "Загрузка товара на сервер";
            Logger.Trace(commandName);
            _avitoTaskStatus.OperationStartTime = DateTime.Now;
            _avitoTaskStatus.OperationMessage = commandName;

            WebClient.DefaultHeaders[HttpRequestHeader.Accept] = "application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, */*";
            WebClient.DefaultHeaders[HttpRequestHeader.Referer] = "https://www.avito.ru/additem";
            WebClient.DefaultHeaders[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";

            SetProductPostFields(_avitoProduct);

            var resp = WebClient.UploadValues(new Uri("https://www.avito.ru/additem"), _postFieldsCollection);
            if ((WebClient.ResponseUri != new Uri("https://www.avito.ru/additem/confirm")))
            {
                _avitoTaskStatus.OperationSucceeded = false;
                if (!Settings.Default.PublicationSkipErrors)
                    _avitoTaskStatus.TryToResolveMethod = () => WebClient.OnHtmlChanged();
                _avitoTaskStatus.ErrorCode = 41;

            }
            else
            {
                Logger.Info("Товар заполнен верно, произошел переход на страницу additem/confirm");
                _avitoTaskStatus.OperationSucceeded = true;
            }
            return _avitoTaskStatus;
        }

        /// <summary>
        /// Операция загрузки изображения на сервер
        /// </summary>
        private AvitoTaskStatus UploadProductImage()
        {
            var commandName = "Загрузка изображения на сервер";
            Logger.Trace(commandName);
            _avitoTaskStatus.OperationStartTime = DateTime.Now;
            _avitoTaskStatus.OperationMessage = commandName;
            var remoteImageId = HttpUploadFile("https://www.avito.ru/additem/image", _avitoProduct.MainProductImage, "image", "image/jpeg", new NameValueCollection());
            if (remoteImageId == null && _avitoProduct.MainProductImage != null)
            {
                _avitoTaskStatus.OperationSucceeded = false;
                _avitoTaskStatus.ErrorCode = 31;

            }
            else
            {
                Logger.Debug("Получен идентификатор загруженного изображения #{0}", remoteImageId);
                _postFieldsCollection.Remove(_postFieldsCollection.AllKeys.FirstOrDefault(x => x.Contains("images")));
                _postFieldsCollection.Remove(_postFieldsCollection.AllKeys.FirstOrDefault(x => x.Contains("rotate")));
                _postFieldsCollection.Add("images[]", remoteImageId);
                _postFieldsCollection.Add("rotate[" + remoteImageId + "]", "0");
                _avitoTaskStatus.OperationSucceeded = true;

            }
            return _avitoTaskStatus;
        }

        /// <summary>
        /// Предварительная операция выделения токена со страницы добавления товара (/additem) и его последующей отправкой в теле POST запроса 
        /// </summary>
        private AvitoTaskStatus SetProductToken()
        {
            var commandName = "Запрос для получения нового Token";
            Logger.Trace(commandName);
            _avitoTaskStatus.OperationStartTime = DateTime.Now;
            _avitoTaskStatus.OperationMessage = commandName;
            var str = WebClient.DownloadString(new Uri("https://www.avito.ru/additem"));
            var reg = new Regex(@"name=\""(?<token_name>token\[\d+\])""\svalue=\""(?<token_value>\w+)\""\/>").Match(str);
            var tn = reg.Groups["token_name"].Value;
            var tv = reg.Groups["token_value"].Value;
            if (String.IsNullOrEmpty(tn) || String.IsNullOrEmpty(tv))
            {
                _avitoTaskStatus.OperationSucceeded = false;
                _avitoTaskStatus.ErrorCode = 21;
            }
            else
            {
                Logger.Debug("Полученые значения token_name={0} token_value={1}", tn, tv);
                _postFieldsCollection.Remove(_postFieldsCollection.AllKeys.FirstOrDefault(x => x.Contains("token")));
                _postFieldsCollection.Add(tn, tv);
                _avitoTaskStatus.OperationSucceeded = true;

            }
            return _avitoTaskStatus;

        }

        /// <summary>
        /// Получение и загрузка капчи из содержимого страницы
        /// </summary>
        /// <param name="htmlContent"></param>
        /// <returns></returns>
        private string GetAndSaveCaptchaImage(string htmlContent)
        {
            Directory.CreateDirectory("captcha");
            var captchaRegex = new Regex(@"\/captcha\?\d+");
            var captchaUrl = captchaRegex.Match(htmlContent).Value;
            if (captchaUrl == String.Empty)
            {
                Logger.Warn("Невозможно получить ссылку на изображение защитного кода с предоставленной страницы");
                return null;
            }
            var captachaFileName = "captcha/" + Path.GetRandomFileName() + ".jpg";
            WebClient.DownloadFile("https://www.avito.ru" + captchaUrl, captachaFileName);
            return captachaFileName;
        }

        /// <summary>
        /// <see cref="http://stackoverflow.com/questions/566462/upload-files-with-httpwebrequest-multipart-form-data"/>
        /// </summary>
        public string HttpUploadFile(string url, string file, string paramName, string contentType, NameValueCollection nvc)
        {
            if (!File.Exists(file))
                return null;
            string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
            byte[] boundarybytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");

            HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(url);
            wr.Accept = "*/*";
            wr.ContentType = "multipart/form-data; boundary=" + boundary;
            wr.Referer = "https://www.avito.ru/additem";
            wr.UserAgent = WebClient.DefaultHeaders[HttpRequestHeader.UserAgent];
            wr.CookieContainer = WebClient.CookieContainer;
            wr.Method = "POST";
            wr.KeepAlive = true;
            wr.Credentials = CredentialCache.DefaultCredentials;


            Stream rs = wr.GetRequestStream();

            string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
            foreach (string key in nvc.Keys)
            {
                rs.Write(boundarybytes, 0, boundarybytes.Length);
                string formitem = string.Format(formdataTemplate, key, nvc[key]);
                byte[] formitembytes = Encoding.UTF8.GetBytes(formitem);
                rs.Write(formitembytes, 0, formitembytes.Length);
            }
            rs.Write(boundarybytes, 0, boundarybytes.Length);

            string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n";
            string header = string.Format(headerTemplate, paramName, file, contentType);//file
            byte[] headerbytes = Encoding.UTF8.GetBytes(header);
            rs.Write(headerbytes, 0, headerbytes.Length);

            FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
            byte[] buffer = new byte[4096];
            int bytesRead = 0;
            while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
            {
                rs.Write(buffer, 0, bytesRead);
            }
            fileStream.Close();

            byte[] trailer = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
            rs.Write(trailer, 0, trailer.Length);
            rs.Close();

            string siteResponse;
            var imageIdRegex = new Regex(@"(?<=id\"":)\d+");
            WebResponse wresp = null;
            try
            {
                wresp = wr.GetResponse();
                Stream stream2 = wresp.GetResponseStream();
                StreamReader reader2 = new StreamReader(stream2);
                siteResponse = reader2.ReadToEnd();
                return imageIdRegex.Match(siteResponse).Value;

            }
            catch (Exception ex)
            {
                Console.WriteLine("Error uploading file", ex);
                if (wresp != null)
                {
                    wresp.Close();
                    wresp = null;
                }
            }
            finally
            {
                wr = null;

            }
            return null;

        }

        /// <summary>
        /// Повторный ввод защитного кода
        /// </summary>
        private void ReEnterCaptchaCode()
        {
            var result = MessageBox.Show("Неверно введен защитный код, повторить ввод?", "Ошибка размещения объявления", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result == MessageBoxResult.Yes)
            {
                CaptchaImagePath = GetAndSaveCaptchaImage(WebClient.ResponseHtml);
                CreateCaptchaConfirmationWindow();

            }
            else
            {
                // Пользователь не захотел ввести код, переходим к след. товару
                _avitoTaskStatus.OperationRepeatNeeded = false;
            }
        }

        /// <summary>
        /// Создание окна для ввода капчи
        /// </summary>
        private void CreateCaptchaConfirmationWindow()
        {
            if (CaptchaImagePath != null)
            {
                Window captchaWindow = new Window() { DataContext = this, Width = 220, Height = 140 };
                captchaWindow.Title = "Окно ввода защитного кода";
                captchaWindow.Content = new ControlCaptcha();
                captchaWindow.Closed += (sender, args) =>
                {
                    _avitoTaskStatus.ErrorCode = null;
                    _avitoTaskStatus.TryToResolveMethod = null;
                    Messenger.Default.Send<NotificationMessage>(new NotificationMessage(this, "SendConfirmationCode"));
                };

                captchaWindow.Show();
            }
        }



        public override string TaskName
        {
            get { return "публикация"; }
        }
        public override string TaskDescription
        {
            get { return _avitoProduct.Title; }
        }
        public override string TaskCompletedUrl
        {
            get { return _avitoProduct.PostedOnUrl; }
        }
        public override List<OperationDelegate> TaskExecutionPlan
        {
            get
            {
                return new List<OperationDelegate>()
                {
                PublicationTimeout,
                SetProductToken,
                UploadProductImage,
                UploadProduct,
                DownloadCaptchaImage,
                SendConfirmationCode,
                FinishPublication
                };
            }
        }


    }
}
